{ config, lib, pkgs, ... }:
with lib;
let
  cfg = config.roles.server.synapse;
in {
  options.roles.server.synapse = {
    enable = mkOption {
      default = false;
      type = types.bool;
    };
    domain = mkOption {
      default = config.roles.server.domain;
      type = types.str;
    };
    dataDir = mkOption {
      default = "/var/lib/matrix-synapse";
      type = types.str;
    };
    element = mkOption {
      default = false;
      type = types.bool;
    };
    maxUploadSize = mkOption {
      default = "500M";
      type = types.str;
    };
    registrationSharedSecretFile = mkOption {
      type = types.str;
    };
  };

  config = mkIf cfg.enable {
    systemd.services.matrix-synapse = {
      after = [ "postgresql.service" ];
      requires = [ "postgresql.service" ];
    };

    networking.firewall.allowedTCPPorts = [ 8448 ];

    services = {
      postgresql = {
        enable = mkForce true;
        ensureDatabases = [ config.services.matrix-synapse.settings.database.args.database ];
        ensureUsers = [{
          name = config.services.matrix-synapse.settings.database.args.user;
          ensureDBOwnership = true;
        }];
      };

      matrix-synapse = {
        enable = true;
        inherit (cfg) dataDir;
        enableRegistrationScript = false;
        settings = rec {
          database.allow_unsafe_locale = true;
          max_upload_size = cfg.maxUploadSize;
          media_store_path = cfg.dataDir + "/media_store";
          public_baseurl = "https://" + server_name;
          registration_shared_secret_path = cfg.registrationSharedSecretFile;
          server_name = "matrix." + cfg.domain;
        };
      };

      nginx = let
        clientConfig."m.homeserver".base_url = config.services.matrix-synapse.settings.public_baseurl;
        serverConfig."m.server" = config.services.matrix-synapse.settings.server_name + ":443";
        mkWellKnown = data: ''
          default_type application/json;
          add_header Access-Control-Allow-Origin *;
          return 200 '${builtins.toJSON data}';
        '';
      in {
        upstreams.synapse.servers = { "127.0.0.1:8008" = {}; };
        virtualHosts = {
          ${cfg.domain}.locations = {
            "= /.well-known/matrix/client".extraConfig = mkWellKnown clientConfig;
            "= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig;
          };

          ${config.services.matrix-synapse.settings.server_name} = {
            forceSSL = true;
            useACMEHost = cfg.domain;
            extraConfig = ''
              client_max_body_size ${cfg.maxUploadSize};
            '';
            listen = [
              { addr = "0.0.0.0"; port = 80; }
              { addr = "[::0]"; port = 80; }
              { addr = "0.0.0.0"; port = 443; ssl = true; }
              { addr = "[::0]"; port = 443; ssl = true; }
              { addr = "0.0.0.0"; port = 8448; ssl = true; }
              { addr = "[::0]"; port = 8448; ssl = true; }
            ];
            locations = {
              "/".return = if cfg.element then
                "301 https://element.${cfg.domain}$request_uri" else
                  "301 https://${cfg.domain}$request_uri";
              "/_matrix".proxyPass = "http://synapse";
            };
          };

          "element.${cfg.domain}" = {
            forceSSL = true;
            useACMEHost = cfg.domain;
            globalRedirect = if cfg.element then null else cfg.domain;
            root = if cfg.element then (pkgs.element-web.override {
              conf.default_server_config = clientConfig;
            }) else null;
          };
        };
      };
    };
  };
}
